#pragma once

#include "../../util/verify.hpp"




namespace boostext
{
	namespace iostreams
	{
		using namespace boost::iostreams;

		/**
		 * Model of Sink providing read-write access to a sequence of characters in memory.
		 * Unlike array_sink, vector_sink is resized dynamically, when needed.
		 */
		template <typename Ch>
		class basic_vector
			: public device<seekable,Ch>
		{
		public:

			typedef Ch   char_type;

		private:

			std::vector<char_type>   &m_buffer;
			stream_offset            m_pos;

		public:

			/**
			 * Create a basic_vector_sink with the given vector.
			 * The sink will resize the vector when necessary.
			 */
			basic_vector(std::vector<char_type>& buffer)
				: m_buffer(buffer)
				, m_pos(0)
			{}



			/**
			 * Write up to n characters from the buffer
			 * s to the output sequence.
			 */
			std::streamsize write(const char_type* s, std::streamsize n)
			{
				if(m_pos+n > m_buffer.size())
					m_buffer.resize(m_pos + n);

				::memcpy(&m_buffer[m_pos], s, n * sizeof(char_type));

				m_pos += n;
				return n;
			}

			/**
			 * Read up to n characters from the stream and
			 * store them in s.
			 */
			std::streamsize read(char_type* s, std::streamsize n)
			{
				boost::iostreams::stream_offset pos = m_pos + n;
				verify_range(pos, (stream_offset)0, (stream_offset)m_buffer.size());

				::memcpy(s, &m_buffer[m_pos], n * sizeof(char_type));

				m_pos += n;
				return n;
			}

			/**
			 * Gain direct read-only access to a portion of the stream.
			 * The returned pointer, points to the first byte, at the stream's
			 * current position and it's length is no more than n.
			 *
			 * @warning the block of memory may only be used, until other function of the stream is called, then the pointer will be invalidated
			 *
			 * @info Calling this function does not modify the stream's position.
			 *
			 * @throws   boost_ext::ios_failure when the range cannot be satisfied
			 */
			const void* directSequence(std::streamsize n) const
			{
				boost::iostreams::stream_offset pos = m_pos + n;
				verify_range(pos, (stream_offset)0, (stream_offset)m_buffer.size());

				return &m_buffer[m_pos];
			}

			/**
			 * Advances the read/write head by off characters,
			 * returning the new position, where the offset is
			 * calculated from:
			 *  - the start of the sequence if way == ios_base::beg
			 *  - the current position if way == ios_base::cur
			 *  - the end of the sequence if way == ios_base::end
			 */
			std::streampos seek(stream_offset off, std::ios_base::seekdir way)
			{
				switch(way)
				{
				case std::ios_base::beg:
					{
						verify_range(off, (stream_offset)0, (stream_offset)m_buffer.size());
						return m_pos = off;
					}
					break;
				case std::ios_base::cur:
					{
						boost::iostreams::stream_offset pos = m_pos + off;
						verify_range(pos, (stream_offset)0, (stream_offset)m_buffer.size());
						return m_pos += off;
					}
					break;
				case std::ios_base::end:
					{
						boost::iostreams::stream_offset pos = m_pos + off;
						verify_range(pos, (stream_offset)0, (stream_offset)m_buffer.size());
						return m_pos += off;
					}
					break;
				default: BOOSTEXT_THROW(invalid_value_exception(way));
				}
			}

			std::streamsize size() const
			{
				return m_buffer.size();
			}
		};
		///////////////////////////////////////////////////////////////////////////////////////////

		typedef basic_vector<char>      vector;
		typedef basic_vector<wchar_t>   wvector;
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
